/**
 * Copyright 2009 - Morten Udnæs
 * 
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not
 * use this file except in compliance with the License. You may obtain a copy of
 * the License at
 * 
 * http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 * License for the specific language governing permissions and limitations under
 * the License.
 */
package com.javabatch.fileio;

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.DataInputStream;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

/**
 * Abstract class to extend when file uses linefeed as records separator.
 *
 * @author eco
 *
 */

public class RecordBasedFileStore implements FileStore {

    /** Open file in read mode. */
    public static final int READ_ACCESS = 0;

    /** Open file in write mode. */
    public static final int WRITE_ACCESS = 1;

    /** File to have CRLF as record delimiter. */
    public static final int USE_CRLF_MODE = 0;

    /** File has fixed record length (and no CRLF). */
    public static final int USE_FIXED_WIDTH_MODE = 1;

    /** Linefeed constant. */
    private static final String CRLF = "\n";

    /** Buffered stream. */
    private BufferedInputStream inBIStream;

    /** Input stream. */
    private DataInputStream     inDIStream;

    /** Name of file to parse. */
    private String              filename;

    /** CRLF/Fixed mode. */
    private int mode;

    /** Read or Write acccess. */
    private int access;

    /** Output stream. */
    private FileOutputStream outFOStream;

    /** Buffered output. */
    private BufferedOutputStream outBOStream;

    /** Record length. */
    private int recordLength;

    /**
     * Constructor to use for fixed with files.
     * @param theFilename full filename and path to requested file
     * @param theAccess Access (read or write)
     * @param theMode (Fixed width or CRLF separated)
     */
    public RecordBasedFileStore(final String theFilename, int theAccess,
            int theRecordLength) {
        this.filename = theFilename;
        this.recordLength = theRecordLength;
        this.mode = USE_FIXED_WIDTH_MODE;
        this.access = theAccess;
    }


    /**
     * Constructor to read/create file.
     * @param theFilename full filename and path to requested file
     * @param theAccess Access (read or write)
     * @param theMode (Fixed width or CRLF separated)
     */
    public RecordBasedFileStore(final String theFilename, int theAccess) {
        this.filename = theFilename;

        this.access = theAccess;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void open() {
        try {
            if (access == READ_ACCESS) {
                inBIStream = new BufferedInputStream(new FileInputStream(
                        filename));
                inDIStream = new DataInputStream(inBIStream);
            } else {
                outFOStream = new FileOutputStream(filename);
                outBOStream = new BufferedOutputStream(outFOStream);
            }

        } catch (IOException ex) {
            throw new RuntimeException(ex);
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void close() {
        try {
            if (inBIStream != null) {
                inBIStream.close();
            }
            if (inDIStream != null) {
                inDIStream.close();
            }
            if (outBOStream != null) {
                outBOStream.close();
            }
            if (outFOStream != null) {
                outFOStream.close();
            }

        } catch (IOException ex) {
            throw new RuntimeException(ex);
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public FileBuffer find(Field field, byte[] value) {
        throw new IllegalArgumentException("Not implemented...");

    }
    public boolean hasMore() {
    	try {
			return inDIStream.available() > 0;
		} catch (IOException e) {
			throw new RuntimeException(e);
		}
    }
    /**
     * {@inheritDoc}
     */
    @Override
    public List<FileBuffer> getBatch(int requestedBatchSize) {
        // TODO Auto-generated method stub
        ArrayList<FileBuffer> list = new ArrayList<FileBuffer>();
        int recordCount = 0;

        try {
            while (inDIStream.available() != 0
                    && requestedBatchSize < recordCount) {
                list.add(readNext());
            }
        } catch (IOException e) {
            throw new RuntimeException(e);

        }
        return list;
    }

    /**
     * {@inheritDoc}
     *
     */
    @Override
    public FileBuffer getFirst() {
        // Simple reset for now
        close();
        open();
        return readNext();
    }

    /**
     * {@inheritDoc}
     */

    @Override
    public void write(final FileBuffer record) {
        if (access == READ_ACCESS) {
            throw new IllegalArgumentException(
                    "Trying to write to a file with READ-only access....!");
        }

        try {
            outBOStream.write(record.getBytes(), 0, record.length());

            if (mode == USE_CRLF_MODE) {
                outBOStream.write(CRLF.getBytes(), 0, CRLF.length());
            }
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * {@inheritDoc}
     */
    @SuppressWarnings("deprecation")
    @Override
    public FileBuffer readNext() {
        try {
            if (inDIStream.available() != 0) {
                if (mode == USE_CRLF_MODE) {
                    return new FileBuffer(inDIStream.readLine().getBytes());
                } else {
                    byte[] record = new byte[recordLength];
                    int readBytes = inDIStream.read(record);
                    if (readBytes != recordLength) {
                        throw new RuntimeException("Read number of bytes: "
                                + readBytes
                                + " was different from record size: "
                                + recordLength);
                    }
                    return new FileBuffer(record);
                }
            } else {
                return null;
            }

        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }
}
